package jinyilw.swing.animator;

import jinyilw.common.Operator;
import jinyilw.common.OperatorSets;

import javax.swing.Timer;

public class SimpleAnimator
{
	private int duration = 60000 * 30;
	private int resolution = 10;
	private final OperatorSets<Integer> targets = new OperatorSets<>();
	private final Runnable endRunnable;

	private boolean running;
	private boolean hasBegun;
	private boolean timeToStop;
	private long startTime;
	private int count;
	private Timer timer;

	public SimpleAnimator(Operator<Integer> target)
	{
		this(10, target, null);
	}

	public SimpleAnimator(int resolution, Operator<Integer> target)
	{
		this(resolution, target, null);
	}

	public SimpleAnimator(int resolution, Operator<Integer> target, Runnable endRunnable)
	{
		if (resolution > 0)
			this.resolution = resolution;
		targets.add(target);
		this.endRunnable = endRunnable;
	}

	/**
	 * Returns the duration of the animation in milliseconds.
	 */
	public int getDuration()
	{
		return duration;
	}

	/**
	 * Sets the duration of the animation in milliseconds.
	 */
	public void setDuration(int duration)
	{
		throwExceptionIfRunning();
		if (duration <= 0)
			throw new IllegalArgumentException();
		this.duration = duration;
	}

	/**
	 * Returns the resolution of the animation in milliseconds (default is 10).
	 * Resolution is the amount of time between timing events.
	 */
	public int getResolution()
	{
		return resolution;
	}

	/**
	 * Sets the resolution of the animation in milliseconds.
	 */
	public void setResolution(int resolution)
	{
		throwExceptionIfRunning();
		if (resolution <= 0)
			throw new IllegalArgumentException();
		this.resolution = resolution;
	}

	/**
	 * Adds a target to the animation that receives timing events.
	 *
	 * @param target the target that receives timing events
	 */
	public void addTarget(Operator<Integer> target)
	{
		if (target == null)
			return;
		synchronized (targets)
		{
			targets.add(target);
		}
	}

	/**
	 * Removes a target from the animation.
	 *
	 * @param target the target that should be removed
	 */
	public void removeTarget(Operator<Integer> target)
	{
		synchronized (targets)
		{
			targets.remove(target);
		}
	}

	/**
	 * Starts the animation.
	 */
	public void start()
	{
		throwExceptionIfRunning();

		running = true;
		hasBegun = false;
		timeToStop = false;
		startTime = System.nanoTime() / 1000000;

		if (timer == null)
		{
			timer = new Timer(resolution, e ->
			{
				if (!hasBegun)
				{
					begin();
					hasBegun = true;
				}
				animating();
			});
		} else
			timer.setDelay(resolution);
		timer.setInitialDelay(0);
		timer.start();
		System.out.println("animator start");
	}

	/**
	 * Stops the animation before it normally ends.
	 */
	public void stop()
	{
		stop(false);
	}

	/**
	 * Cancels the animation before it normally ends.
	 */
	public void cancel()
	{
		stop(true);
	}

	private void stop(boolean cancel)
	{
		if (!running)
			return;

		if (timer != null)
			timer.stop();

		if (!cancel)
			end();

		count = 0;
		running = false;
		timeToStop = false;
		System.out.println("animator stop");
	}

	public void restart()
	{
		cancel();
		start();
	}

	public boolean isRunning()
	{
		return running;
	}

	private void animating()
	{
		targets.operate(count);
		count++;
		long elapsedTime = System.nanoTime() / 1000000 - startTime;
		timeToStop = (elapsedTime >= duration);
		if (timeToStop)
			stop();
	}

	private void begin()
	{

	}

	private void end()
	{
		if (endRunnable != null)
			endRunnable.run();
	}

	private void throwExceptionIfRunning()
	{
		if (isRunning())
			throw new IllegalStateException();
	}
}
